package hu.pixelesztergalyos.continuity;

import java.awt.Point;
import java.awt.event.KeyEvent;
import java.awt.geom.Point2D;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.NodeList;

// Level class for storing the mosaics and the character, gate and key
public class Level {
  public Game owner;                         // owner: for control integrity
  private Mosaic[][] mosaics;                // mosaics: a matrix containing the mosaics of the current level
  private ViewState vs = ViewState.LEVEL;    // vs: upon initialization sets the view to level
  private int rows;                          // rows: number of rows containing mosaics
  private int columns;                       // columns: number of columns in a mosaic row
  private int id;                            // id: identification number of the current level
  private String name;                       // name: name of the map
  private boolean isCompleted;               // isCompleted: sets the state of the gameplay wether the level is completed
  private Key key;                           // key: reference to key
  private Gate gate;                         // gate: reference teo gate
  private Character character1;              // character1: reference to 1st character
  private Character character2;              // character2: reference to 2nd character
  private Point currentMosaic;               // currentMosaic: position of active mosaic within the mosaics matrix

  public static final float MOSAIC_WIDTH = 250f;  // MOSAIC_WIDTH: based on xml layout
  public static final float MOSAIC_HEIGHT = 150f; // MOSAIC_HEIGHT: based on xml layout
	//  0,0 is the upper left corner of the elements

	// Constructor
	public Level(String fileName) {
		try { 
			File currentLevel = new File("src/hu/pixelesztergalyos/continuity/Levels/" + fileName);
			//making a File from the given physical file
			DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
			//making a factory to obtain a parser
			DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
			//making a documentbuilder for DOM handling
			Document doc = dBuilder.parse(currentLevel);
			//making a document from the XML
			this.rows = Integer.parseInt(doc.getDocumentElement().getAttribute("rows"));
			//parsing the number of the rows from the XML
			this.columns = Integer.parseInt(doc.getDocumentElement().getAttribute("cols"));
			//parsing the number of the columns from the XML
			this.id = Integer.parseInt(doc.getDocumentElement().getAttribute("id"));
			//parsing the id of the Level from the XML
			this.name = doc.getDocumentElement().getAttribute("name");
			//parsing the name of the Level from the XML
			mosaics = new Mosaic[rows][columns];
			//making the mosaics from the previously parsed datas (rows and columns)
			NodeList mosaicsNodeList = doc.getElementsByTagName("mosaic");
			//creating a list from the parsed the mosaic elements
			int mosaicNodeLength = mosaicsNodeList.getLength();
			//for further use, we need the number of the mosaics in the XML
			for (int i = 0; i < mosaicNodeLength; i++) {
				NamedNodeMap mosaicAttributes = mosaicsNodeList.item(i).getAttributes();
				int mosaicPosRow = Integer.parseInt(mosaicAttributes.getNamedItem("row").getTextContent());
				int mosaicPosCol = Integer.parseInt(mosaicAttributes.getNamedItem("col").getTextContent());
				NodeList mosaicsChildren = mosaicsNodeList.item(i).getChildNodes();
				
				String [] mosaicString = new String [mosaicsChildren.getLength()];
				for (int j = 0; j < mosaicsChildren.getLength(); j++) {
					mosaicString[j] = mosaicsChildren.item(j).getTextContent();
				} //building the String which later has given to the mosaics array
				mosaics[mosaicPosRow-1][mosaicPosCol-1] = new Mosaic(mosaicString, this, new Point(mosaicPosRow-1,mosaicPosCol-1));
			} //parsing the rest of the XML, and building the mosaics array from it
		} 
		catch (Exception ex) { 
			System.out.println(ex.getMessage()); 
		}
	  
	}
		
	public void draw() {
	}
	// Updates the game
	public void update() throws IOException {
		// If in mosaic view then update character positions
    if (vs == ViewState.MOSAIC) {
			character1.tick();
			character2.tick();
		} 
    // else move mosaics  
    else {
			if (owner.ks[KeyEvent.VK_LEFT]) 
			{
				moveMosaic(Direction.LEFT);           // execute movement
				owner.ks[KeyEvent.VK_LEFT] = false;   // reset keystate to unpressed
			}
			if (owner.ks[KeyEvent.VK_RIGHT]) 
			{
				moveMosaic(Direction.RIGHT);
				owner.ks[KeyEvent.VK_RIGHT] = false;
			}
			if (owner.ks[KeyEvent.VK_DOWN]) 
			{
				moveMosaic(Direction.DOWN);
				owner.ks[KeyEvent.VK_DOWN] = false;
			}
			if (owner.ks[KeyEvent.VK_UP]) 
			{
				moveMosaic(Direction.UP);
				owner.ks[KeyEvent.VK_UP] = false;
			}
		}
	}

	/**
	 * Reset the level
	 * 
	 * @return
	 * @throws IOException 
	 */
	public void reset() throws IOException {
		id=id-1;
		isCompleted=true;
	}

	/**
	 * 
	 * @param Direction
	 * @return
	 */
	// move the mosaics when in Level View
	public void moveMosaic(Direction dir) throws IOException {
		if (dir == null) { 
			return;
		}
		if (dir == Direction.RIGHT) {
			for (int i = 0; i < rows; i++) {
				for (int j = 0; j < columns; j++) { // -1 because if the mosaic is on the edge we cannot move it right
					if (mosaics[i][j] == null) {
						if ((j-1) >= 0)
						{
						mosaics[i][j] = mosaics[i][j-1];
						mosaics[i][j].coord.y++;
						mosaics[i][j-1] = null;
						if (mosaics[i][j].getHasCharacter())
						{
							currentMosaic.x = i;
							currentMosaic.y = j;
						}
						return;
						}
					}
				}
			}
		} else if (dir == Direction.LEFT) {
			for (int i = 0; i < rows; i++) {
				for (int j = 0; j < columns; j++) { // -1 because if the mosaic is on the edge we cannot move it right
					if (mosaics[i][j] == null) {
						if ((j+1) < columns)
						{
						mosaics[i][j] = mosaics[i][j+1];
						mosaics[i][j].coord.y--;
						mosaics[i][j+1] = null;
						if (mosaics[i][j].getHasCharacter())
						{
							currentMosaic.x = i;
							currentMosaic.y = j;
						}
						return;
						}
					}
				}
			}
		} else if (dir == Direction.UP) {
			for (int i = 0; i < rows; i++) {
				for (int j = 0; j < columns; j++) { // -1 because if the mosaic is on the edge we cannot move it right
					if (mosaics[i][j] == null) {
						if ((i+1) < rows)
						{
						mosaics[i][j] = mosaics[i+1][j];
						mosaics[i][j].coord.x--;
						mosaics[i+1][j] = null;
						if (mosaics[i][j].getHasCharacter())
						{
							currentMosaic.x = i;
							currentMosaic.y = j;
						}
						return;
						}
					}
				}
			}
		} else if (dir == Direction.DOWN) {
			for (int i = 0; i < rows; i++) {
				for (int j = 0; j < columns; j++) { // -1 because if the mosaic is on the edge we cannot move it right
					if (mosaics[i][j] == null) {
						if ((i-1) >= 0)
						{
						mosaics[i][j] = mosaics[i-1][j];
						mosaics[i][j].coord.x++;
						mosaics[i-1][j] = null;
						if (mosaics[i][j].getHasCharacter())
						{
							currentMosaic.x = i;
							currentMosaic.y = j;
						}
						return;
						}
					}
				}
			}
		}
	}

	/**
	 * 
	 * @param ViewState
	 * @return
	 * @throws IOException
	 */
	// Sets the view state to the value given in the parameter
	public void setViewState(ViewState vs) throws IOException {
		this.vs = vs;
	}

	/**
	 * 
	 * @param Mosaic1
	 * @param Mosaic2
	 * @param Direction
	 * @return
	 * @throws IOException
	 */

	// Compares the borders of the mosaics to know whether they fit together or not
	public boolean testTransition(TransitionBlock transBlock,
			Character character) throws IOException { 
		int i = currentMosaic.x;
		int j = currentMosaic.y;

		if (transBlock.type == TransitionType.RIGHT) {
			if ((j + 1) < columns) { // if it is not on the edge
				if (mosaics[i][j+1] != null) { 
					
					for (int k = 0; k < 15; k++)
						if (mosaics[currentMosaic.x][currentMosaic.y].rightSide[k] != mosaics[i][j+1].leftSide[k]) { // comparing transBlocks
							return false;
						}
							
							Character temp_c = new Character(character);
							Point2D.Float temp_o = character.getOrigin();
							Point2D.Float p = new Point2D.Float(10.f, temp_o.y);
							temp_c.setOrigin(p);
							if (mosaics[i][j+1].collisionDetection(temp_c,character)) {

								if (character.number == 1) {
									character1.setOrigin(p);
									character2.setOrigin(p);
									mosaics[currentMosaic.x][currentMosaic.y].setHasCharacter(false);
									mosaics[currentMosaic.x][currentMosaic.y].setHasCharacter2(false);
									// remove the character from the current mosaic
									mosaics[i][j+1].setHasCharacter(true);
									mosaics[i][j+1].setHasCharacter2(true);
									
									currentMosaic = new Point(i,(j+1)); 
									// the current mosaic will be the new mosaic
									return true;
								} else {
									character1.setOrigin(p);
									character2.setOrigin(p);
									mosaics[currentMosaic.x][currentMosaic.y].setHasCharacter(false);
									mosaics[currentMosaic.x][currentMosaic.y].setHasCharacter2(false);
									// remove the character from the current mosaic
									mosaics[i][j+1].setHasCharacter(true);
									mosaics[i][j+1].setHasCharacter2(true);
									
									currentMosaic = new Point(i,(j+1)); 
									return true;
								}
							}
				
				}
			} else{
				return false;
			}
		}
	
		if (transBlock.type == TransitionType.LEFT) {
			if ((j - 1) >= 0) { // if it is not on the edge
				if (mosaics[i][j-1] != null) { 
					for (int k = 0; k < 15; k++)
						if (mosaics[currentMosaic.x][currentMosaic.y].leftSide[k] != mosaics[i][j-1].rightSide[k]) { // comparing transBlocks
							return false;
						}
							
							Character temp_c = new Character(character);
							Point2D.Float temp_o = character.getOrigin();
							Point2D.Float p = new Point2D.Float(240.f, temp_o.y);
							temp_c.setOrigin(p);
							if (mosaics[i][j-1].collisionDetection(temp_c,character)) {

								if (character.number == 1) {
									character1.setOrigin(p);
									character2.setOrigin(p);
									mosaics[currentMosaic.x][currentMosaic.y].setHasCharacter(false);
									mosaics[currentMosaic.x][currentMosaic.y].setHasCharacter2(false);
									// remove the character from the current mosaic
									mosaics[i][j-1].setHasCharacter(true);
									mosaics[i][j-1].setHasCharacter2(true);
									currentMosaic = new Point(i,(j-1)); 
									// the current mosaic will be the new mosaic
									return true;
								} else {
									character1.setOrigin(p);
									character2.setOrigin(p);
									mosaics[currentMosaic.x][currentMosaic.y].setHasCharacter(false);
									mosaics[currentMosaic.x][currentMosaic.y].setHasCharacter2(false);
									// remove the character from the current mosaic
									mosaics[i][j-1].setHasCharacter(true);
									mosaics[i][j-1].setHasCharacter2(true);
									currentMosaic = new Point(i,(j-1));
									return true;
								}
							}
				}
			} else{
				return false;
			}
		}
			
		if (transBlock.type == TransitionType.TOP) {
			if ((i - 1) >= 0) { // if it is not on the edge
				if (mosaics[i-1][j] != null) { 
					for (int k = 0; k < 26; k++){
						if (mosaics[currentMosaic.x][currentMosaic.y].topSide[k] != mosaics[i-1][j].bottomSide[k]) { // comparing transBlocks
							return false;
						}}
							Character temp_c = new Character(character);
							Point2D.Float temp_o = character.getOrigin();
							Point2D.Float p = new Point2D.Float(temp_o.x, 120);
							temp_c.setOrigin(p);
							if (mosaics[i-1][j].collisionDetection(temp_c,character)) {

								if (character.number == 1) {
									character1.setOrigin(p);
									character2.setOrigin(p);
									mosaics[currentMosaic.x][currentMosaic.y].setHasCharacter(false); 
									mosaics[currentMosaic.x][currentMosaic.y].setHasCharacter2(false);
									// remove the character from the current mosaic
									mosaics[i-1][j].setHasCharacter(true);
									mosaics[i-1][j].setHasCharacter2(true);
									currentMosaic = new Point(i-1,(j)); 
									// the current mosaic will be the new mosaic
									return true;
								} else {
									character1.setOrigin(p);
									character2.setOrigin(p);
									mosaics[currentMosaic.x][currentMosaic.y].setHasCharacter(false); 
									mosaics[currentMosaic.x][currentMosaic.y].setHasCharacter2(false);
									// remove the character from the current mosaic
									mosaics[i-1][j].setHasCharacter(true);
									mosaics[i-1][j].setHasCharacter2(true);
									
									currentMosaic = new Point(i-1,(j));
									return true;
								}
							}
				}
			} else{
				return false;
			}
		}
		if (transBlock.type == TransitionType.BOTTOM) {
			if ((i + 1) < rows) { // if it is not on the edge
				if (mosaics[i+1][j] != null) { 

					for (int k = 0; k < 26; k++)
						if (mosaics[currentMosaic.x][currentMosaic.y].bottomSide[k] != mosaics[i+1][j].topSide[k]) { // comparing transBlocks
							this.reset();
							return false;
						}
							
							Character temp_c = new Character(character);
							Point2D.Float temp_o = character.getOrigin();
							Point2D.Float p = new Point2D.Float(temp_o.x, 10.f);
							temp_c.setOrigin(p);
							if (mosaics[i+1][j].collisionDetection(temp_c,character)) {

								if (character.number == 1) {
									character1.setOrigin(p);
									character2.setOrigin(p);
									mosaics[currentMosaic.x][currentMosaic.y].setHasCharacter(false);
									mosaics[currentMosaic.x][currentMosaic.y].setHasCharacter2(false);
									// remove the character from the current mosaic
									mosaics[i+1][j].setHasCharacter(true);
									mosaics[i+1][j].setHasCharacter2(true);
									currentMosaic = new Point(i+1,(j)); 
									// the current mosaic will be the new mosaic
									return true;
								} else {
									character1.setOrigin(p);
									character2.setOrigin(p);
									mosaics[currentMosaic.x][currentMosaic.y].setHasCharacter(false);
									mosaics[currentMosaic.x][currentMosaic.y].setHasCharacter2(false);
									// remove the character from the current mosaic
									mosaics[i+1][j].setHasCharacter(true);
									mosaics[i+1][j].setHasCharacter2(true);
									
									currentMosaic = new Point(i+1,(j)); 
									return true;
								}
							}
				}
				this.reset();
				return false;
			} else{
				this.reset();
				return false;
			}
		}
		return false;
	}

	/**
	 * 
	 * @return
	 */
	public ViewState getViewState() {
		return vs;
	}
	
	public int getId(){
		return this.id;
	}
	/**
	 * 
	 * @return
	 */

	// set the current mosaic to the one given in the parameter
	public void setCurrentMosaic(Point currentMosaic) {
		this.currentMosaic = currentMosaic;
	}

	/**
	 * 
	 * @return
	 */
	public Mosaic getCurrentMosaic() {
		return mosaics[currentMosaic.x][currentMosaic.y];
	}
	
	public Point getCurrentMosaicCoord() {
		return currentMosaic;
	}

	// if the character collides with the gate with the key in his possession
	// the level is completed and
	// this method sets to flag to notify the level of it
	public void setIsCompleted(boolean isCompleted) throws IOException {
		this.isCompleted = isCompleted;
	}

	public boolean getIsCompleted() {
		return isCompleted;
	}

	public Key getKey() {
		return key;
	}

	public void setKey(Key k) {
		this.key = k;
	}

	public Gate getGate() {
		return gate;
	}

	public void setGate(Gate g) {
		this.gate = g;
	}

	public Mosaic[][] getMosaics() {
		return this.mosaics;
	}

	public Character getCharacter(int which) {
		if (which == 1)
			return character1;
		else
			return character2;
	}

	public void setCharacter(int which, Character ch) {
		if (which == 1)
			character1 = ch;
		else
			character2 = ch;
	}

	// For proto writeout
	public void writeLevel(String filename) {
		try {
			FileWriter fstream = new FileWriter(filename);
			BufferedWriter out = new BufferedWriter(fstream);
			out.write("<?xml version=\"1.0\"?>");
			out.write("<level id=\"" + id + "\" name=\"" + name + "\" rows=\""
					+ rows + "\" cols=\"" + columns + "\">");
			for (int i = 0; i < rows; i++)
				for (int j = 0; j < columns; j++) {
					if (mosaics[i][j] != null)
						mosaics[i][j].writeLevel(filename, out);
					else
						out.write("<!-- NULL mosaic @ [" + (i + 1) + ","
								+ (j + 1) + "] -->");
				}

			out.write("</level>");
			out.close();

		} catch (Exception exc) {
			System.out.println(exc.toString());
		}
	}
}